// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license

#include "stdafx.h"
#include "Gizmo.h"
#include "Color.h"
#include "GizmoManager.h"
#include "ActionManager.h"
#include "ActionShortcuts.h"
#include "Ui.h"


Gizmo::Gizmo(GizmoManager* manager)
: m_manager(manager)
{
    // This is what will be drawn until the drag cycle begins
    // (if the pivot or the frame move, the gizmo won't be updated)
    m_iniGizmoFrame = computeGizmoFrame();
}

Gizmo::~Gizmo()
{
}

//  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

bool Gizmo::onBeginDragImpl(const ScreenPoint & clickPoint)
{
    m_iniLocalTransform = m_manager->getDraggedItem()->getLocalTransform();
    m_iniGlobalTransform = m_manager->getDraggedItem()->getGlobalTransform();
    m_currGizmoFrame = m_iniGizmoFrame = computeGizmoFrame();

    return true;
}

void Gizmo::onDragImpl(const ScreenPoint & dragPoint)
{
    gx::Mat4 local, world;

    m_changedProp = NodeEvent::NodeTransform;
    onNodeDrag(dragPoint, local, world, m_changedProp);

    m_currGizmoFrame = world * m_iniGizmoFrame;

    Transformable* item = m_manager->getDraggedItem();
    TransformNode* node = dynamic_cast<TransformNode*>(item);

    // If item is a node, provide more accurate notification info.
    if(node != NULL)
    {
        node->setLocalTransform(local * m_iniLocalTransform, m_changedProp);
    }
    else
    {
        item->setLocalTransform(local * m_iniLocalTransform); 
    }
}

void Gizmo::onEndDragImpl()
{
    m_iniGizmoFrame = m_currGizmoFrame;

    Transformable* item = m_manager->getDraggedItem();
    if(TransformNode* rawNode = dynamic_cast<TransformNode*>(item))
    {
        ref<TransformNode> node = rawNode->getRef<TransformNode>();
        postTransformNodeAction(
            m_manager->getView()->getUi().getActionManager(),
            node,
            node->getLocalTransform(),
            m_iniLocalTransform,
            m_changedProp
        );
    }
}

//  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

Dim Gizmo::findPreferredScreenAxis(const gx::Vec3 & axis) const
{
    gx::Mat4 cameraMat = m_manager->m_view->getCameraMatrix();
    return std::fabs(axis.dot(cameraMat.get_col_0()))
        > std::fabs(axis.dot(cameraMat.get_col_1())) ? Dims::X : Dims::Y;
}

//  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

namespace
{

Color RgbBright[] = { makeBrighter(getRed()), makeBrighter(getGreen()), 
                      makeBrighter(getBlue()) };

Color RgbDark[] = { makeDarker(getRed()), makeDarker(getGreen()), 
                    makeDarker(getBlue()) };

Color greys[] = { getGrey128(), getGrey128(), getGrey128() };

gx::Vec3 XYZ[] = { gx::I, gx::J, gx::K };

} // anonymous namespace
 

void Gizmo::draw(View3d * view, const gx::Mat4 & gizmoFrame, 
                 const Color colors[3]) const
{
    bool selMode = view->isDrawingInPickMode();

    glClear(GL_DEPTH_BUFFER_BIT);

    glPushAttrib(GL_DEPTH_BUFFER_BIT | GL_CURRENT_BIT
               | GL_LIGHTING_BIT | GL_LINE_BIT 
               | GL_COLOR_BUFFER_BIT);
//    glDisable(GL_DEPTH_TEST);
    glDisable(GL_LIGHTING);

    glPushMatrix();

        glLineWidth(selMode ? 1.f : 1.f);

        double m[16];
        glMultMatrixd(gizmoFrame.to_array(m));
    
        double scale = view->computeConstantScaleFactor(gizmoFrame.translation);
        double gizmoSize = .35 * scale; // will take 15% of the screen height
        glScaled(gizmoSize, gizmoSize, gizmoSize);
        
        for(int d = Dims::X; d <= Dims::Z; ++d)
        {
            setColorOrPickCode(view, colors[d], GLubyte(GizmoHandleIds::X + d));
            glBegin(GL_LINES);
                glVertex3d(0., 0., 0.);
                glVertex3dv(XYZ[d].to_array());
            glEnd();
        }

        glPushMatrix();
            drawGizmoEnds(view, colors, selMode ? .15 : .15);
        glPopMatrix();

        if(!m_dragging)
        {
            double squareHandleSz = .45;
            double k = .03; // half width
            glBegin(GL_QUADS);
                setColorOrPickCode(view, colors[Dims::X], GizmoHandleIds::XY);
                glVertex3d(squareHandleSz - k, 0., 0.);
                glVertex3d(squareHandleSz + k, 0., 0.);
                setColorOrPickCode(view, colors[Dims::Y], GizmoHandleIds::XY);
                glVertex3d(0., squareHandleSz + k, 0.);
                glVertex3d(0., squareHandleSz - k, 0.);
            glEnd();

            glBegin(GL_QUADS);
                setColorOrPickCode(view, colors[Dims::Y], GizmoHandleIds::YZ);
                glVertex3d(0., squareHandleSz - k, 0.);
                glVertex3d(0., squareHandleSz + k, 0.);
                setColorOrPickCode(view, colors[Dims::Z], GizmoHandleIds::YZ);
                glVertex3d(0., 0., squareHandleSz + k);
                glVertex3d(0., 0., squareHandleSz - k);
            glEnd();

            glBegin(GL_QUADS);
                setColorOrPickCode(view, colors[Dims::X], GizmoHandleIds::XZ);
                glVertex3d(squareHandleSz - k, 0., 0.);
                glVertex3d(squareHandleSz + k, 0., 0.);
                setColorOrPickCode(view, colors[Dims::Z], GizmoHandleIds::XZ);
                glVertex3d(0., 0., squareHandleSz + k);
                glVertex3d(0., 0., squareHandleSz - k);
            glEnd();

            //if(hasXYZGizmoPart())
            //{
            //    glLineWidth(4.f);
            //    if(!view->isDrawingInPickMode())
            //    {
            //        //glEnable(GL_BLEND);
            //    }
            //    setColorOrPickCode(view, getWhite(.3f), GizmoHandleIds::XYZ);
            //    glBegin(GL_LINES);
            //        glVertex3d(squareHandleSz + 4 * k, 0., 0.);
            //        glVertex3d(0., squareHandleSz + 4. * k, 0.);

            //        glVertex3d(squareHandleSz + 4 * k, 0., 0.);
            //        glVertex3d(0., 0., squareHandleSz + 4. * k);

            //        glVertex3d(0., squareHandleSz + 4 * k, 0.);
            //        glVertex3d(0., 0., squareHandleSz + 4. * k);
            //    glEnd();
            //}
        }
    glPopMatrix();

    glPopAttrib();
}

void Gizmo::draw(View3d * view) const
{
    if(m_dragging)
    {
        draw(view, m_iniGizmoFrame, greys);
    }

    // We must use computeGizmoFrame here
    draw(view, m_dragging ? m_currGizmoFrame : computeGizmoFrame(), 
         view->getHoverItem() == this ? RgbBright: RgbDark);
}

size_t Gizmo::getDrawOrder() const
{
    return kDefaultDrawOrder + 1;
}

void Gizmo::drawGizmoEnds(View3d * view, const Color colors[3], 
                          double size) const
{
}

//  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  


bool Gizmo::hasXYZGizmoPart() const
{
    return false;
}

//  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

gx::Mat4 Gizmo::computeGizmoFrame() const
{
    Transformable* item = m_manager->getDraggedItem();

    // First the frame
    gx::Mat3 frame;
    switch(m_manager->getAxesType())
    {
        case GizmoAxesTypes::NODE:
        {
            // Don't use m_iniGlobalTransform always because it might not 
            // be set yet (the drag cycle might not have started)
            frame = item->getGlobalTransform().rotation;
            break;
        }
        case GizmoAxesTypes::WORLD:
        {
            frame = gx::eye;
            break;
        }
        case GizmoAxesTypes::PARENT:
        {
            if(Node* node = dynamic_cast<Node*>(item))
            {
                frame = node->getParentGlobalTransform().rotation;
            }
            else
            {
                frame = gx::eye;
            }
            break;
        }
        case GizmoAxesTypes::VIEW:
        {
            frame = m_manager->getView()->getCameraMatrix().rotation;
            break;
        }
        case GizmoAxesTypes::OTHER_NODE:
        {
            frame = m_manager->getAxesNode()->getGlobalTransform().rotation;
            break;
        }
        default: ASSERT(false);
    }

    // Normalize because transform might be scaled
    frame.normalize_cols(); 

    // Now the pivot
    gx::Vec3 pivot;
    switch(m_manager->getPivotType())
    {
        case GizmoPivotTypes::NODE:
        {
            pivot = item->getGlobalTransform().translation;
            break;
        }
        case GizmoPivotTypes::WORLD:
        {
            pivot = gx::zero;
            break;
        }
        case GizmoPivotTypes::PARENT:
        {
            if(Node* node = dynamic_cast<Node*>(item))
            {
                pivot = node->getParentGlobalTransform().translation;
            }
            else
            {
                pivot = gx::zero;
            }
            break;
        }
        case GizmoPivotTypes::OTHER_NODE:
        {
            pivot = m_manager->getPivotNode()->getGlobalTransform().translation;
            break;
        }
        default: ASSERT(false);
    }

    return gx::Mat4().make_transl(pivot) * frame;
}

//  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

Node* GizmoManager::getPivotNode() const
{
    ASSERT(m_pivotId == GizmoPivotTypes::OTHER_NODE);
    return m_pivotNode;
}

GizmoPivotType GizmoManager::getPivotType() const
{
    return m_pivotId;
}

void GizmoManager::setPivotNode(Node* node)
{
    m_pivotNode = node;
    setPivotNode(GizmoPivotTypes::OTHER_NODE);
    m_view->invalidate();
}

void GizmoManager::setPivotNode(GizmoPivotType id)
{
    // OTHER_NODE not supported; use the other overload 
    // of setPivotNode() to enter that mode
    ASSERT(id >= GizmoPivotTypes::NODE && id <= GizmoPivotTypes::OTHER_NODE);
    
    if(id != GizmoPivotTypes::OTHER_NODE)
    {
        m_pivotNode = NULL;
    }
    m_pivotId = id;
        m_view->invalidate();

}

void GizmoManager::cyclePivot()
{
    if(m_pivotId == GizmoPivotTypes::OTHER_NODE)
    {
        setPivotNode(GizmoPivotTypes::NODE);
    }
    else
    {
        setPivotNode(GizmoPivotType((m_pivotId + 1) % 3));
    }
}

bool Gizmo::isDimSelected(Dim dim) const
{
    GizmoHandleId handleId = (GizmoHandleId)m_partId;

    ASSERT(handleId >= GizmoHandleIds::X && handleId <= GizmoHandleIds::XYZ);
    if(handleId == GizmoHandleIds::XYZ)
    {
        return true;
    }
    switch(dim)
    {
    case Dims::X:
        return handleId == GizmoHandleIds::X || handleId == GizmoHandleIds::XY
            || handleId == GizmoHandleIds::XZ;
    case Dims::Y:
        return handleId == GizmoHandleIds::Y || handleId == GizmoHandleIds::XY
            || handleId == GizmoHandleIds::YZ;
    case Dims::Z:
        return handleId == GizmoHandleIds::Z || handleId == GizmoHandleIds::XZ
            || handleId == GizmoHandleIds::YZ;
    }
    ASSERT(false);
    return false;
}

// ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
